﻿// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version>$Revision$</version>
// </file>

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

using ICSharpCode.TextEditor.Document;

namespace ICSharpCode.TextEditor
{
	/// <summary>
	/// This class paints the textarea.
	/// </summary>
	[ToolboxItem(false)]
	public class TextAreaControl : Panel
	{
        Padding padding = new Padding(2, 2, 2, 2);

		TextEditorControl motherTextEditorControl;
		
		HRuler     hRuler     = null;

        //VScrollBar vScrollBar = new VScrollBar();
        //HScrollBar hScrollBar = new HScrollBar();

        TextEditorScrollBar vScrollBar = new TextEditorVScrollBar();
        TextEditorScrollBar hScrollBar = new TextEditorHScrollBar();

        TextArea   textArea;
		bool       doHandleMousewheel = true;
		bool       disposed;

        bool hasTextChanged = false;              
        int scrollMarginHeight = 3;        

        Timer TextChangedTimer = null;

        bool adjustScrollBarsOnNextUpdate;
        Point scrollToPosOnNextUpdate;

        public bool HasTextChanged
        {
            get
            {
                return hasTextChanged;
            }
        }

        public TextArea TextArea {
			get {
				return textArea;
			}
		}
		
		public SelectionManager SelectionManager {
			get {
				return textArea.SelectionManager;
			}
		}
		
		public Caret Caret {
			get {
				return textArea.Caret;
			}
		}
		
		[Browsable(false)]
		public IDocument Document {
			get {
				if (motherTextEditorControl != null)
					return motherTextEditorControl.Document;
				return null;
			}
		}
		
		public ITextEditorProperties TextEditorProperties {
			get {
				if (motherTextEditorControl != null)
					return motherTextEditorControl.TextEditorProperties;
				return null;
			}
		}

        //public VScrollBar VScrollBar {
        //	get {
        //		return vScrollBar;
        //	}
        //}

        //public HScrollBar HScrollBar {
        //	get {
        //		return hScrollBar;
        //	}
        //}

        public TextEditorScrollBar VScrollBar
        {
            get
            {
                return vScrollBar;
            }
        }

        public TextEditorScrollBar HScrollBar
        {
            get
            {
                return hScrollBar;
            }
        }

        public ScrollEventHandler ScrollBarValueChanged;
		
		public bool DoHandleMousewheel {
			get {
				return doHandleMousewheel;
			}
			set {
				doHandleMousewheel = value;
			}
		}
		
		public TextAreaControl(TextEditorControl motherTextEditorControl)
		{
            TextChangedTimer = new Timer();
            TextChangedTimer.Enabled = false;
            TextChangedTimer.Interval = 150;

            this.motherTextEditorControl = motherTextEditorControl;
			
			this.textArea                = new TextArea(motherTextEditorControl, this);
			Controls.Add(textArea);

            //vScrollBar.Scroll += new ScrollEventHandler(VScrollBar_Scroll);
            vScrollBar.ValueChanged += new EventHandler(VScrollBarValueChanged);            
            Controls.Add(this.vScrollBar);

            //hScrollBar.Scroll += new ScrollEventHandler(HScrollBar_Scroll);
            hScrollBar.ValueChanged += new EventHandler(HScrollBarValueChanged);
            Controls.Add(this.hScrollBar);

            ResizeRedraw = true;           

            Document.TextContentChanged += DocumentTextContentChanged;
			Document.DocumentChanged += AdjustScrollBarsOnDocumentChange;
			Document.UpdateCommited  += DocumentUpdateCommitted;

            TextChangedTimer.Tick += TextChangedTimer_Tick;
        }

        private void TextChangedTimer_Tick(object sender, EventArgs e)
        {
            if (textArea == null || textArea.Document == null)
                return;            
            if (textArea.Document.TextEditorProperties.Font == null)
                return;
            if (!textArea.Document.TextEditorProperties.ShowGuidelines)
                return;

            if (hasTextChanged)
            {
                hasTextChanged = false;
                textArea.Document.GuidelinesManager.Update(textArea);
            }                        
        }

        protected override void Dispose(bool disposing)
		{
            TextChangedTimer.Enabled = false;
            TextChangedTimer.Dispose();
            if (disposing)
            {
                if (!disposed)
                {
                    disposed = true;
                    Document.TextContentChanged -= DocumentTextContentChanged;
                    Document.DocumentChanged -= AdjustScrollBarsOnDocumentChange;
                    Document.UpdateCommited -= DocumentUpdateCommitted;
                    motherTextEditorControl = null;
                    if (vScrollBar != null)
                    {
                        vScrollBar.Dispose();
                        vScrollBar = null;
                    }
                    if (hScrollBar != null)
                    {
                        hScrollBar.Dispose();
                        hScrollBar = null;
                    }
                    if (hRuler != null)
                    {
                        hRuler.Dispose();
                        hRuler = null;
                    }
                }
            }           
            base.Dispose(disposing);
		}

        bool VirtualTopEquals(Point sourcePoint, Point targetPoint)
        {
            if (targetPoint.X != sourcePoint.X || targetPoint.Y != sourcePoint.Y)
            {
                return false;
            }
            return true;
        }

        void DocumentTextContentChanged(object sender, EventArgs e)
		{
			// after the text content is changed abruptly, we need to validate the
			// caret position - otherwise the caret position is invalid for a short amount
			// of time, which can break client code that expects that the caret position is always valid
			Caret.ValidateCaretPos();
		}
		
		protected override void OnResize(System.EventArgs e)
		{
			base.OnResize(e);
			ResizeTextArea();
		}
		
		public void ResizeTextArea()
		{
            //int y = 0;
            //int h = 0;
            //if (hRuler != null) {
            //    hRuler.Bounds = new Rectangle(0,
            //                                  0,
            //                                  Width - SystemInformation.HorizontalScrollBarArrowWidth,
            //                                  textArea.TextView.FontHeight);
				
            //    y = hRuler.Bounds.Bottom;
            //    h = hRuler.Bounds.Height;
            //}
			
            //textArea.Bounds = new Rectangle(0, y,
            //                                Width - SystemInformation.HorizontalScrollBarArrowWidth,
            //                                Height - SystemInformation.VerticalScrollBarArrowHeight - h);
			//SetScrollBarBounds();

            SetScrollBarBounds();

		}

        //public void _SetScrollBarBounds()
        //{
        //    vScrollBar.Bounds = new Rectangle(textArea.Bounds.Right, 0, SystemInformation.HorizontalScrollBarArrowWidth, Height - SystemInformation.VerticalScrollBarArrowHeight);
        //    hScrollBar.Bounds = new Rectangle(0,
        //                                      textArea.Bounds.Bottom,
        //                                      Width - SystemInformation.HorizontalScrollBarArrowWidth,
        //                                      SystemInformation.VerticalScrollBarArrowHeight);
        //}

        /// <summary>
        /// 修改于 2018-1-23 By wxdong
        /// </summary>
        public void SetScrollBarBounds()
        {
            int borderWidth = 0;

            Rectangle rectangle = this.ClientRectangle;

            //int heigth = (Document.GetVisibleLine(Document.TotalNumberOfLines + 1)) * textArea.TextView.FontHeight;
            int heigth = (Document.GetVisibleLine(Document.TotalNumberOfLines + 1)) * textArea.TextView.FontHeight;

            if (heigth > rectangle.Height)
            {
                vScrollBar.Visible = true;
            }
            else
            {
                vScrollBar.Visible = false;
            }

            //if (motherTextEditorControl.BorderVisable)
            //{
            //    borderWidth = 1;
            //}

            int width = textArea.GetVisibleColumnCount();

            if (width > textArea.TextView.VisibleColumnCount)
            {
                hScrollBar.Visible = true;
            }
            else
            {
                hScrollBar.Visible = false;
            }

            int y = 0;
            int h = 0;
            if (hRuler != null && hRuler.Visible )
            {
                //hRuler.Bounds = new Rectangle(0,
                //                              0,
                //                              Width - (vScrollBar.Visible ? ScrollBarInformation.VerticalScrollBarWidth : 0),
                //                              textArea.TextView.FontHeight);

                hRuler.Bounds = new Rectangle(0,
                                              0,
                                              Width - (vScrollBar.Visible ? ScrollBarInformation.VerticalScrollBarWidth : 0),
                                              26);

                y = hRuler.Bounds.Bottom;
                h = hRuler.Bounds.Height;
            }

            textArea.Bounds = new Rectangle(padding.Left, y + padding.Top,
                                           Width - padding.Left - padding.Right - (vScrollBar.Visible ? ScrollBarInformation.VerticalScrollBarWidth : 0),
                                           Height - padding.Top - padding.Bottom - (hScrollBar.Visible ? ScrollBarInformation.HorizontalScrollBarHeight : 0) - h);


            vScrollBar.Bounds = new Rectangle(textArea.Bounds.Right + padding.Right,
                                              0,
                                              ScrollBarInformation.VerticalScrollBarWidth,
                                              Height - (hScrollBar.Visible ? ScrollBarInformation.HorizontalScrollBarHeight + borderWidth - 1 : 0));
            hScrollBar.Bounds = new Rectangle(0,
                                              textArea.Bounds.Bottom + padding.Bottom,
                                              Width - (vScrollBar.Visible ? ScrollBarInformation.VerticalScrollBarWidth + borderWidth - 1 : 0),
                                              ScrollBarInformation.HorizontalScrollBarHeight);

        }
		
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
		void AdjustScrollBarsOnDocumentChange(object sender, DocumentEventArgs e)
		{
			if (motherTextEditorControl.IsInUpdate == false) {
				AdjustScrollBarsClearCache();
				AdjustScrollBars();
			} else {
				adjustScrollBarsOnNextUpdate = true;
			}
		}
		
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
		void DocumentUpdateCommitted(object sender, EventArgs e)
		{
			if (motherTextEditorControl.IsInUpdate == false) {
				Caret.ValidateCaretPos();
				
				// AdjustScrollBarsOnCommittedUpdate
				if (!scrollToPosOnNextUpdate.IsEmpty) {
					ScrollTo(scrollToPosOnNextUpdate.Y, scrollToPosOnNextUpdate.X);
				}
				if (adjustScrollBarsOnNextUpdate) {
					AdjustScrollBarsClearCache();
					AdjustScrollBars();
				}
			}
		}
		
		//int[] lineLengthCache;
		//const int LineLengthCacheAdditionalSize = 100;
		
		void AdjustScrollBarsClearCache()
		{
			//if (lineLengthCache != null) {
			//	if (lineLengthCache.Length < this.Document.TotalNumberOfLines + 2 * LineLengthCacheAdditionalSize) {
			//		lineLengthCache = null;
			//	} else {
			//		Array.Clear(lineLengthCache, 0, lineLengthCache.Length);
			//	}
			//}
		}

        public void AdjustScrollBars()
        {
            adjustScrollBarsOnNextUpdate = false;

            vScrollBar.Minimum = 0;
            // number of visible lines in document (folding!)
            vScrollBar.Maximum = textArea.MaxVScrollValue;

            vScrollBar.LargeChange = Math.Max(0, textArea.TextView.DrawingPosition.Height);
            vScrollBar.SmallChange = Math.Max(0, textArea.TextView.FontHeight);

            hScrollBar.Minimum = 0;
            hScrollBar.Maximum = textArea.MaxHScrollValue;

            hScrollBar.LargeChange = Math.Max(0, textArea.TextView.VisibleColumnCount - 1);
            hScrollBar.SmallChange = Math.Max(0, (int)textArea.TextView.SpaceWidth);

            //vScrollBar.BorderVisable = !motherTextEditorControl.BorderVisable;
            //HScrollBar.BorderVisable = !motherTextEditorControl.BorderVisable;

            SetScrollBarBounds();

        }
		
		public void OptionsChanged()
		{
			textArea.OptionsChanged();

            if (textArea.TextEditorProperties.ShowHorizontalRuler)
            {
                if (hRuler == null)
                {
                    hRuler = new HRuler(textArea);
                    Controls.Add(hRuler);
                    ResizeTextArea();
                }
                else
                {
                    hRuler.Invalidate();
                }
            }
            else
            {
                if (hRuler != null)
                {
                    Controls.Remove(hRuler);
                    hRuler.Dispose();
                    hRuler = null;
                    ResizeTextArea();
                }
            }

            //Document.GuidelinesManager.Update();

            AdjustScrollBars();
		}

        public void UpdateVirtualTop()
        {
            Point value = new Point(hScrollBar.Value * textArea.TextView.WideSpaceWidth, vScrollBar.Value);
            textArea.UpdateVirtualTop(value);
        }

        //void VScrollBar_Scroll(object sender, ScrollEventArgs e)
        //{
        //    textArea.VirtualTop = new Point(textArea.VirtualTop.X, vScrollBar.Value);
        //}

        //void HScrollBar_Scroll(object sender, ScrollEventArgs e)
        //{
        //    textArea.VirtualTop = new Point(hScrollBar.Value * textArea.TextView.WideSpaceWidth, textArea.VirtualTop.Y);
        //}

        void VScrollBarValueChanged(object sender, EventArgs e)
        {
            if (!TextEditorProperties.ComparisonState)
                textArea.VirtualTop = new Point(textArea.VirtualTop.X, vScrollBar.Value);

            if (this.ScrollBarValueChanged != null)
                this.ScrollBarValueChanged(sender, new ScrollEventArgs(ScrollEventType.ThumbPosition, vScrollBar.Value));
        }
		
		void HScrollBarValueChanged(object sender, EventArgs e)
		{
            if (!TextEditorProperties.ComparisonState)
                textArea.VirtualTop = new Point(hScrollBar.Value * textArea.TextView.WideSpaceWidth, textArea.VirtualTop.Y);

            if (this.ScrollBarValueChanged != null)
                this.ScrollBarValueChanged(sender, new ScrollEventArgs(ScrollEventType.ThumbPosition, vScrollBar.Value));
        }
		
		Util.MouseWheelHandler mouseWheelHandler = new Util.MouseWheelHandler();

        //public void HandleMouseWheel(MouseEventArgs e)
        //{
        //    int scrollDistance = mouseWheelHandler.GetWheelDeltaScrollDistance(e);
        //    if (scrollDistance == 0)
        //        return;
        //    if ((Control.ModifierKeys & Keys.Control) != 0 && TextEditorProperties.MouseWheelTextZoom)
        //    {
        //        if (scrollDistance > 0)
        //        {
        //            motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name,
        //                                                    motherTextEditorControl.Font.Size + 1);
        //        }
        //        else
        //        {
        //            motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name,
        //                                                    Math.Max(6, motherTextEditorControl.Font.Size - 1));
        //        }
        //    }
        //    else
        //    {
        //        if (TextEditorProperties.MouseWheelScrollDown)
        //            scrollDistance = -scrollDistance;
        //        int newValue = vScrollBar.Value + vScrollBar.SmallChange * scrollDistance;
        //        vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum - vScrollBar.LargeChange + 1, newValue));
        //    }
        //}

        public void HandleMouseWheel(MouseEventArgs e)
        {
            int scrollDistance = mouseWheelHandler.GetWheelDeltaScrollDistance(e);
            if (scrollDistance == 0)
                return;
            if ((Control.ModifierKeys & Keys.Control) != 0 && TextEditorProperties.MouseWheelTextZoom)
            {
                Font changedFont = textArea.Document.TextEditorProperties.Font;
                if (scrollDistance > 0)
                {
                    changedFont = new Font(changedFont.Name,
                                                            changedFont.Size + 1);
                }
                else
                {
                    changedFont = new Font(changedFont.Name,
                                                            Math.Max(6, changedFont.Size - 1));
                }                
                textArea.Document.TextEditorProperties.Font = changedFont;
                OptionsChanged();
            }
            else
            {
                int newValue;
                int multiplier = Math.Abs(e.Delta) / NativeMethods.WHEEL_MAX_DELTA;

                if (System.Windows.Forms.SystemInformation.MouseWheelScrollLines > 0)
                {
                    newValue = this.vScrollBar.Value - (TextEditorProperties.MouseWheelScrollDown ? 1 : -1) * Math.Sign(e.Delta) * System.Windows.Forms.SystemInformation.MouseWheelScrollLines * vScrollBar.SmallChange * multiplier;
                }
                else
                {
                    newValue = this.vScrollBar.Value - (TextEditorProperties.MouseWheelScrollDown ? 1 : -1) * Math.Sign(e.Delta) * vScrollBar.LargeChange;
                }
                vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum, newValue));
            }
        }

        protected override void OnMouseWheel(MouseEventArgs e)
		{
			base.OnMouseWheel(e);
			if (DoHandleMousewheel) {
				HandleMouseWheel(e);
			}
		}

        public void DoTextAreaTextChanged(EventArgs e)
        {
            Document.GuidelinesManager.Clear(textArea);
            this.hasTextChanged = true;
        }

        public void ScrollToCaret()
		{
			ScrollTo(textArea.Caret.Line, textArea.Caret.Column);
		}

        public void ScrollTo(int line, int column)
        {
            if (motherTextEditorControl.IsInUpdate)
            {
                scrollToPosOnNextUpdate = new Point(column, line);
                return;
            }
            else
            {
                scrollToPosOnNextUpdate = Point.Empty;
            }

            ScrollTo(line);

            int curCharMin = (int)(this.hScrollBar.Value - this.hScrollBar.Minimum);
            int curCharMax = curCharMin + textArea.TextView.VisibleColumnCount;

            int pos = textArea.TextView.GetVisualColumn(line, column);

            if (textArea.TextView.VisibleColumnCount < 0)
            {
                hScrollBar.Value = 0;
            }
            else
            {
                if (pos < curCharMin)
                {
                    hScrollBar.Value = (int)(Math.Max(0, pos - scrollMarginHeight));
                }
                else if (pos > curCharMax)
                {
                    hScrollBar.Value = (int)Math.Max(0, Math.Min(hScrollBar.Maximum, (pos - textArea.TextView.VisibleColumnCount + scrollMarginHeight)));
                }                
            }
        }

		/// <summary>
		/// Ensure that <paramref name="line"/> is visible.
		/// </summary>
		public void ScrollTo(int line)
		{
			line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line));
			line = Document.GetVisibleLine(line);
			int curLineMin = textArea.TextView.FirstPhysicalLine;
			if (textArea.TextView.LineHeightRemainder > 0) {
				curLineMin ++;
			}
			
			if (line - scrollMarginHeight + 3 < curLineMin) {
				this.vScrollBar.Value =  Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ;
				VScrollBarValueChanged(this, EventArgs.Empty);
			} else {
				int curLineMax = curLineMin + this.textArea.TextView.VisibleLineCount;
				if (line + scrollMarginHeight - 1 > curLineMax) {
					if (this.textArea.TextView.VisibleLineCount == 1) {
						this.vScrollBar.Value =  Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight - 1) * textArea.TextView.FontHeight)) ;
					} else {
						this.vScrollBar.Value = Math.Min(this.vScrollBar.Maximum,
						                                 (line - this.textArea.TextView.VisibleLineCount + scrollMarginHeight - 1)* textArea.TextView.FontHeight) ;
					}
					VScrollBarValueChanged(this, EventArgs.Empty);
				}
			}
		}
		
		/// <summary>
		/// Scroll so that the specified line is centered.
		/// </summary>
		/// <param name="line">Line to center view on</param>
		/// <param name="treshold">If this action would cause scrolling by less than or equal to
		/// <paramref name="treshold"/> lines in any direction, don't scroll.
		/// Use -1 to always center the view.</param>
		public void CenterViewOn(int line, int treshold)
		{
			line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line));
			// convert line to visible line:
			line = Document.GetVisibleLine(line);
			// subtract half the visible line count
			line -= textArea.TextView.VisibleLineCount / 2;
			
			int curLineMin = textArea.TextView.FirstPhysicalLine;
			if (textArea.TextView.LineHeightRemainder > 0) {
				curLineMin ++;
			}
			if (Math.Abs(curLineMin - line) > treshold) {
				// scroll:
				this.vScrollBar.Value =  Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ;
				VScrollBarValueChanged(this, EventArgs.Empty);
			}
		}
		
		public void JumpTo(int line)
		{
			line = Math.Max(0, Math.Min(line, Document.TotalNumberOfLines - 1));
			string text = Document.GetText(Document.GetLineSegment(line));
			JumpTo(line, text.Length - text.TrimStart().Length);
		}
		
		public void JumpTo(int line, int column)
		{
			textArea.Focus();
			textArea.SelectionManager.ClearSelection();
			textArea.Caret.Position = new TextLocation(column, line);
			textArea.SetDesiredColumn();
			ScrollToCaret();
		}

        public void Highlight(int offset, int length)
        {
            if (offset < 0 || length < 1)
                return;
            int endPos = offset + length;

            var startPoint = Document.OffsetToPosition(offset);
            var endPoint = Document.OffsetToPosition(endPos);

            SelectionManager.SetSelection(startPoint, endPoint);
            //Caret.Position = Document.OffsetToPosition(endPos);
        }

        public void AddLineCustomColor(CustomLineColor color,int lineNumber)
        {
            Document.AddLineCustomColor(color, lineNumber);
        }

        public void LineCustomColorsClear()
        {
            Document.LineCustomColorsClear();
        }

        internal void SetTextChangedTimerEnable(bool enable)
        {
            if (this.TextChangedTimer.Enabled != enable)
                this.TextChangedTimer.Enabled = enable;
        }

        public event MouseEventHandler ShowContextMenu;
		
		protected override void WndProc(ref Message m)
		{
			if (m.Msg == 0x007B) { // handle WM_CONTEXTMENU
				if (ShowContextMenu != null) {
					long lParam = m.LParam.ToInt64();
					int x = unchecked((short)(lParam & 0xffff));
					int y = unchecked((short)((lParam & 0xffff0000) >> 16));
					if (x == -1 && y == -1) {
						Point pos = Caret.ScreenPosition;
						ShowContextMenu(this, new MouseEventArgs(MouseButtons.None, 0, pos.X, pos.Y + textArea.TextView.FontHeight, 0));
					} else {
						Point pos = PointToClient(new Point(x, y));
						ShowContextMenu(this, new MouseEventArgs(MouseButtons.Right, 1, pos.X, pos.Y, 0));
					}
				}
			}
			base.WndProc(ref m);
		}
		
		protected override void OnEnter(EventArgs e)
		{
            // SD2-1072 - Make sure the caret line is valid if anyone
            // has handlers for the Enter event.
            motherTextEditorControl.SetActiveTextAreaControl(this);
            Caret.ValidateCaretPos();
			base.OnEnter(e);
		}

    }
}
